// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: dhjang

#pragma once

#include <fmt/printf.h>

#include <set>
#include <algorithm>
#include "coin2/strategy/order_executor/executor.h"
#include "coin2/strategy/linear_model/calc_util.h"
#include "presto/quant/base/time.h"
#include "coin2/strategy/linear_model/mm_util.h"

#include "coin2/exchange/base/log/strategy_logger.h"

namespace coin2::strategy::linear_model {

using coin::proto::OrderDirection;
using coin::proto::OrderDuration;
using coin::proto::OrderType;

//static std::ranodm_device rd;
//static std::mt19937 gen = std::mt19937(rd());
//static std::uniform_real_dist<double> dis(0.0, 1.0);


inline int ManageLmOrder(
    ::coin2::strategy::order_executor::OrderExecutorSystem* oe,
    const coin2::exchange::base::symbology::IProductHolder& pholder,
    const Bbo& bbo,
    const int64_t timestamp,

    int64_t* prev_ask_post_time,
    int64_t* prev_bid_post_time,
    int64_t* prev_ask_fill_time,
    int64_t* prev_bid_fill_time,
    const double leverage_rate,

    const double signal_skewed_lb,
    const double signal_skewed_ub,
    const double volatility,
    const double spread,
    const double book_pressure,

    const bool buy_block,
    const bool sell_block,

    const LmFocusMidfreqConfig& fconf) {
  // return value: number of transactions
  int ret = 0;
  if (bbo.exchange_time > 0 && bbo.fetched_time > bbo.exchange_time + 500e6) {
    ret += oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_SELL, timestamp);
    ret += oe->CancelAllOrders(pholder, OrderDirection::DIRECTION_BUY, timestamp);
    return ret;
  }
  std::set<int64_t> cancel_list;
  int64_t cancel_cooldown = fconf.cancel_cooldown_sec() * 1'000'000'000LL;

  // cancel old orders
  for (const auto* order : oe->GetAllProductWorkingOrders(pholder)) {
    if (order->order_created_time() + fconf.max_posting_period() * 1'000'000'000LL < timestamp) {
      cancel_list.insert(order->proc_order_id());
    }
  }

  double max_lot_size = oe->GetConfig(pholder).qty().max_lot_size();
  double ask0p = bbo.askp;
  double bid0p = bbo.bidp;
  double midp = (ask0p + bid0p) / 2;
  double min_bbo_dist_bps = std::max(fconf.min_bbo_dist_bps(),
      volatility * fconf.volat_const());
  min_bbo_dist_bps = std::min(fconf.max_bbo_dist_bps(), min_bbo_dist_bps);

  // // TODO(jhkim): for inverse semantic???
  // double min_quote = fconf.min_order_quote();
  // double maker_qty_ratio = fconf.maker_qty_ratio();
  // if (max_lot_size * maker_qty_ratio * ask0p < min_quote) return ret;

  auto& tickprc = pholder.product_info().tick_price();
  // threshold_bps cant be zero. will be waste of money
//  double random_signal = dis(gen);
//  if (random_signal > fconf.random_threshold()) {
//    DLOG("random_signal");
//  }

  double buy_condition = 1;
  double sell_condition = 1;
  double pass_price = midp;
  bool stick = false;

  if (buy_condition) {
    double pass_price1 = ask0p * (1 - fconf.min_bbo_dist_bps() * 1e-4);
    double pass_price2 = midp * (1 - fconf.min_bbo_dist_bps_mid() * 1e-4);
    double pass_price3 = bid0p * (1 - fconf.relax_post_bps() * 1e-4);

    double adjust_const = 0;
    adjust_const += volatility * fconf.volatility_const() / midp;
    adjust_const += spread * fconf.spread_const();
    adjust_const += book_pressure * fconf.book_pressure_const() * 1e-4;
    double pass_price4 = midp * (1 - adjust_const);

    if (adjust_const > 0) {
      pass_price = pass_price4;
    } else if (fconf.min_bbo_dist_bps() > 0) {
      pass_price = pass_price1;
    } else if (fconf.min_bbo_dist_bps_mid() > 0) {
      pass_price = pass_price2;
    } else if (fconf.relax_post_bps() > 0) {
      pass_price = pass_price3;
    } else {
      pass_price = midp;
    }

    for (const auto* order : oe->GetAllProductWorkingOrders(pholder)) {
      if (order->direction() != OrderDirection::DIRECTION_BUY) continue;
      if (cancel_list.count(order->proc_order_id())) continue;
      if (std::abs(order->order_price() - pass_price) < pass_price * fconf.sticky_bps() * 1e-4) {
        stick = true;
      }
      double price = order->order_price();
      if ((price >= pass_price * (1 + fconf.pull_bps() * 1e-4) || (price <= pass_price * (1 - fconf.push_bps() * 1e-4)))
          && order->order_created_time() + cancel_cooldown < timestamp) {
        cancel_list.insert(order->proc_order_id());
      }
    }

    if (!stick) {

      auto oe_res = oe->SubmitOrder(
          pholder,
          tickprc.RoundPassively(pass_price, OrderDirection::DIRECTION_BUY),
          max_lot_size,
          OrderDirection::DIRECTION_BUY,
          OrderDuration::GTC_ORDER,
          OrderType::LIMIT_ORDER,
          tickprc.RoundPassively(bid0p, OrderDirection::DIRECTION_BUY),
          leverage_rate,
          true,  // post_only
          "maker",
          timestamp,
          false,
          false);
      if (oe_res.qty > 0) ret++;
    }
  }

  if (sell_condition) {
    double pass_price1 = bid0p * (1 + fconf.min_bbo_dist_bps() * 1e-4);
    double pass_price2 = midp * (1 + fconf.min_bbo_dist_bps_mid() * 1e-4);
    double pass_price3 = ask0p * (1 + fconf.relax_post_bps() * 1e-4);

    double adjust_const = 0;
    adjust_const += volatility * fconf.volatility_const() / midp;
    adjust_const += spread * fconf.spread_const();
    adjust_const += book_pressure * fconf.book_pressure_const() * 1e-4;
    double pass_price4 = midp * (1 + adjust_const);

    if (adjust_const > 0) {
      pass_price = pass_price4;
    } else if (fconf.min_bbo_dist_bps() * 1e-4 > 0) {
      pass_price = pass_price1;
    } else if (fconf.min_bbo_dist_bps_mid() * 1e-4 > 0) {
      pass_price = pass_price2;
    } else if (fconf.relax_post_bps() * 1e-4 > 0) {
      pass_price = pass_price3;
    } else {
      pass_price = midp;
    }

    for (const auto* order : oe->GetAllProductWorkingOrders(pholder)) {
      if (order->direction() != OrderDirection::DIRECTION_SELL) continue;
      if (cancel_list.count(order->proc_order_id())) continue;
      if (std::abs(order->order_price() - pass_price) < pass_price * fconf.sticky_bps() * 1e-4) {
        stick = true;
      }
      double price = order->order_price();
      if ((price <= pass_price * (1 - fconf.pull_bps() * 1e-4) || (price >= pass_price * (1 + fconf.push_bps() * 1e-4)))
          && order->order_created_time() + cancel_cooldown < timestamp) {
        cancel_list.insert(order->proc_order_id());
      }
    }

    if (!stick) {
      auto oe_res = oe->SubmitOrder(
          pholder,
          tickprc.RoundPassively(pass_price, OrderDirection::DIRECTION_SELL),
          max_lot_size,
          OrderDirection::DIRECTION_SELL,
          OrderDuration::GTC_ORDER,
          OrderType::LIMIT_ORDER,
          tickprc.RoundPassively(bid0p, OrderDirection::DIRECTION_SELL),
          leverage_rate,
          true,  // post_only
          "maker",
          timestamp,
          false,
          false);
      if (oe_res.qty > 0) ret++;
    }
  }

  for (auto order_id : cancel_list)
    if (oe->CancelOrder(pholder, timestamp, order_id)) ret++;

  return ret;
}

}  // namespace coin2::strategy::linear_model
